/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.drill.exec.util;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.DrillBuf;
import io.netty.buffer.UnpooledByteBufAllocator;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;

import org.apache.drill.common.util.CoreDecimalUtility;
import org.apache.drill.exec.expr.fn.impl.ByteFunctionHelpers;
import org.apache.drill.exec.expr.holders.Decimal38SparseHolder;

public class DecimalUtility extends CoreDecimalUtility{

  public final static int MAX_DIGITS = 9;
  public final static int MAX_DIGITS_INT = 10;
  public final static int MAX_DIGITS_BIGINT = 19;
  public final static int DIGITS_BASE = 1000000000;
  public final static int DIGITS_MAX = 999999999;
  public final static int INTEGER_SIZE = (Integer.SIZE/8);

  public final static String[] decimalToString = {"",
          "0",
          "00",
          "000",
          "0000",
          "00000",
          "000000",
          "0000000",
          "00000000",
          "000000000"};

  public final static long[] scale_long_constants = {
        1,
        10,
        100,
        1000,
        10000,
        100000,
        1000000,
        10000000,
        100000000,
        1000000000,
        10000000000l,
        100000000000l,
        1000000000000l,
        10000000000000l,
        100000000000000l,
        1000000000000000l,
        10000000000000000l,
        100000000000000000l,
        1000000000000000000l};

  /*
   * Simple function that returns the static precomputed
   * power of ten, instead of using Math.pow
   */
  public static long getPowerOfTen(int power) {
    assert power >= 0 && power < scale_long_constants.length;
    return scale_long_constants[(power)];
  }

  /*
   * Math.pow returns a double and while multiplying with large digits
   * in the decimal data type we encounter noise. So instead of multiplying
   * with Math.pow we use the static constants to perform the multiplication
   */
  public static long adjustScaleMultiply(long input, int factor) {
    int index = Math.abs(factor);
    assert index >= 0 && index < scale_long_constants.length;
    if (factor >= 0) {
      return input * scale_long_constants[index];
    } else {
      return input / scale_long_constants[index];
    }
  }

  public static long adjustScaleDivide(long input, int factor) {
    int index = Math.abs(factor);
    assert index >= 0 && index < scale_long_constants.length;
    if (factor >= 0) {
      return input / scale_long_constants[index];
    } else {
      return input * scale_long_constants[index];
    }
  }

  /* Given the number of actual digits this function returns the
   * number of indexes it will occupy in the array of integers
   * which are stored in base 1 billion
   */
  public static int roundUp(int ndigits) {
      return (ndigits + MAX_DIGITS - 1)/MAX_DIGITS;
  }

  /** Returns a string representation of the given integer
   * If the length of the given integer is less than the
   * passed length, this function will prepend zeroes to the string
   */
  public static StringBuilder toStringWithZeroes(int number, int desiredLength) {
    String value = ((Integer) number).toString();
    int length = value.length();

    StringBuilder str = new StringBuilder();
    str.append(decimalToString[desiredLength - length]);
    str.append(value);

    return str;
  }

  public static StringBuilder toStringWithZeroes(long number, int desiredLength) {
    String value = ((Long) number).toString();
    int length = value.length();

    StringBuilder str = new StringBuilder();

    // Desired length can be > MAX_DIGITS
    int zeroesLength = desiredLength - length;
    while (zeroesLength > MAX_DIGITS) {
        str.append(decimalToString[MAX_DIGITS]);
        zeroesLength -= MAX_DIGITS;
    }
    str.append(decimalToString[zeroesLength]);
    str.append(value);

    return str;
  }

  public static BigDecimal getBigDecimalFromIntermediate(ByteBuf data, int startIndex, int nDecimalDigits, int scale) {

    // In the intermediate representation we don't pad the scale with zeroes, so set truncate = false
    return getBigDecimalFromDrillBuf(data, startIndex, nDecimalDigits, scale, false);
  }

  public static BigDecimal getBigDecimalFromSparse(DrillBuf data, int startIndex, int nDecimalDigits, int scale) {

    // In the sparse representation we pad the scale with zeroes for ease of arithmetic, need to truncate
    return getBigDecimalFromDrillBuf(data, startIndex, nDecimalDigits, scale, true);
  }

  public static BigDecimal getBigDecimalFromDrillBuf(DrillBuf bytebuf, int start, int length, int scale) {
    byte[] value = new byte[length];
    bytebuf.getBytes(start, value, 0, length);
    BigInteger unscaledValue = new BigInteger(value);
    return new BigDecimal(unscaledValue, scale);
  }

  public static BigDecimal getBigDecimalFromByteBuffer(ByteBuffer bytebuf, int start, int length, int scale) {
    byte[] value = new byte[length];
    bytebuf.get(value);
    BigInteger unscaledValue = new BigInteger(value);
    return new BigDecimal(unscaledValue, scale);
  }

  /** Create a BigDecimal object using the data in the DrillBuf.
   * This function assumes that data is provided in a non-dense format
   * It works on both sparse and intermediate representations.
   */
  public static BigDecimal getBigDecimalFromDrillBuf(ByteBuf data, int startIndex, int nDecimalDigits, int scale,
      boolean truncateScale) {

    // For sparse decimal type we have padded zeroes at the end, strip them while converting to BigDecimal.
    int actualDigits;

    // Initialize the BigDecimal, first digit in the DrillBuf has the sign so mask it out
    BigInteger decimalDigits = BigInteger.valueOf((data.getInt(startIndex)) & 0x7FFFFFFF);

    BigInteger base = BigInteger.valueOf(DIGITS_BASE);

    for (int i = 1; i < nDecimalDigits; i++) {

        BigInteger temp = BigInteger.valueOf(data.getInt(startIndex + (i * INTEGER_SIZE)));
        decimalDigits = decimalDigits.multiply(base);
        decimalDigits = decimalDigits.add(temp);
    }

    // Truncate any additional padding we might have added
    if (truncateScale == true && scale > 0 && (actualDigits = scale % MAX_DIGITS) != 0) {
        BigInteger truncate = BigInteger.valueOf((int)Math.pow(10, (MAX_DIGITS - actualDigits)));
        decimalDigits = decimalDigits.divide(truncate);
    }

    // set the sign
    if ((data.getInt(startIndex) & 0x80000000) != 0) {
        decimalDigits = decimalDigits.negate();
    }

    BigDecimal decimal = new BigDecimal(decimalDigits, scale);

    return decimal;
  }

  /* This function returns a BigDecimal object from the dense decimal representation.
   * First step is to convert the dense representation into an intermediate representation
   * and then invoke getBigDecimalFromDrillBuf() to get the BigDecimal object
   */
  public static BigDecimal getBigDecimalFromDense(DrillBuf data, int startIndex, int nDecimalDigits, int scale, int maxPrecision, int width) {

    /* This method converts the dense representation to
     * an intermediate representation. The intermediate
     * representation has one more integer than the dense
     * representation.
     */
    byte[] intermediateBytes = new byte[((nDecimalDigits + 1) * INTEGER_SIZE)];

    // Start storing from the least significant byte of the first integer
    int intermediateIndex = 3;

    int[] mask = {0x03, 0x0F, 0x3F, 0xFF};
    int[] reverseMask = {0xFC, 0xF0, 0xC0, 0x00};

    int maskIndex;
    int shiftOrder;
    byte shiftBits;

    // TODO: Some of the logic here is common with casting from Dense to Sparse types, factor out common code
    if (maxPrecision == 38) {
        maskIndex = 0;
        shiftOrder = 6;
        shiftBits = 0x00;
        intermediateBytes[intermediateIndex++] = (byte) (data.getByte(startIndex) & 0x7F);
    } else if (maxPrecision == 28) {
        maskIndex = 1;
        shiftOrder = 4;
        shiftBits = (byte) ((data.getByte(startIndex) & 0x03) << shiftOrder);
        intermediateBytes[intermediateIndex++] = (byte) (((data.getByte(startIndex) & 0x3C) & 0xFF) >>> 2);
    } else {
        throw new UnsupportedOperationException("Dense types with max precision 38 and 28 are only supported");
    }

    int inputIndex = 1;
    boolean sign = false;

    if ((data.getByte(startIndex) & 0x80) != 0) {
        sign = true;
    }

    while (inputIndex < width) {

      intermediateBytes[intermediateIndex] = (byte) ((shiftBits) | (((data.getByte(startIndex + inputIndex) & reverseMask[maskIndex]) & 0xFF) >>> (8 - shiftOrder)));

      shiftBits = (byte) ((data.getByte(startIndex + inputIndex) & mask[maskIndex]) << shiftOrder);

      inputIndex++;
      intermediateIndex++;

      if (((inputIndex - 1) % INTEGER_SIZE) == 0) {
          shiftBits = (byte) ((shiftBits & 0xFF) >>> 2);
          maskIndex++;
          shiftOrder -= 2;
      }

    }
    /* copy the last byte */
    intermediateBytes[intermediateIndex] = shiftBits;

    if (sign == true) {
        intermediateBytes[0] = (byte) (intermediateBytes[0] | 0x80);
    }

    final ByteBuf intermediate = UnpooledByteBufAllocator.DEFAULT.buffer(intermediateBytes.length);
    try {
        intermediate.setBytes(0, intermediateBytes);

      BigDecimal ret = getBigDecimalFromIntermediate(intermediate, 0, nDecimalDigits + 1, scale);
      return ret;
    } finally {
      intermediate.release();
    }

  }

  /**
   * Function converts the BigDecimal and stores it in out internal sparse representation
   */
  public static void getSparseFromBigDecimal(BigDecimal input, ByteBuf data, int startIndex, int scale, int precision,
      int nDecimalDigits) {

    // Initialize the buffer
    for (int i = 0; i < nDecimalDigits; i++) {
      data.setInt(startIndex + (i * INTEGER_SIZE), 0);
    }

    boolean sign = false;

    if (input.signum() == -1) {
        // negative input
        sign = true;
        input = input.abs();
    }

    // Truncate the input as per the scale provided
    input = input.setScale(scale, BigDecimal.ROUND_HALF_UP);

    // Separate out the integer part
    BigDecimal integerPart = input.setScale(0, BigDecimal.ROUND_DOWN);

    int destIndex = nDecimalDigits - roundUp(scale) - 1;

    // we use base 1 billion integer digits for out integernal representation
    BigDecimal base = new BigDecimal(DIGITS_BASE);

    while (integerPart.compareTo(BigDecimal.ZERO) == 1) {
        // store the modulo as the integer value
        data.setInt(startIndex + (destIndex * INTEGER_SIZE), (integerPart.remainder(base)).intValue());
        destIndex--;
        // Divide by base 1 billion
        integerPart = (integerPart.divide(base)).setScale(0, BigDecimal.ROUND_DOWN);
    }

    /* Sparse representation contains padding of additional zeroes
     * so each digit contains MAX_DIGITS for ease of arithmetic
     */
    int actualDigits;
    if ((actualDigits = (scale % MAX_DIGITS)) != 0) {
        // Pad additional zeroes
        scale = scale + (MAX_DIGITS - actualDigits);
        input = input.setScale(scale, BigDecimal.ROUND_DOWN);
    }

    //separate out the fractional part
    BigDecimal fractionalPart = input.remainder(BigDecimal.ONE).movePointRight(scale);

    destIndex = nDecimalDigits - 1;

    while (scale > 0) {
        // Get next set of MAX_DIGITS (9) store it in the DrillBuf
        fractionalPart = fractionalPart.movePointLeft(MAX_DIGITS);
        BigDecimal temp = fractionalPart.remainder(BigDecimal.ONE);

        data.setInt(startIndex + (destIndex * INTEGER_SIZE), (temp.unscaledValue().intValue()));
        destIndex--;

        fractionalPart = fractionalPart.setScale(0, BigDecimal.ROUND_DOWN);
        scale -= MAX_DIGITS;
    }

    // Set the negative sign
    if (sign == true) {
        data.setInt(startIndex, data.getInt(startIndex) | 0x80000000);
    }
  }

  public static long getDecimal18FromBigDecimal(BigDecimal input, int scale, int precision) {
    // Truncate or pad to set the input to the correct scale
    input = input.setScale(scale, BigDecimal.ROUND_HALF_UP);

    return input.unscaledValue().longValue();
  }

  public static int getDecimal9FromBigDecimal(BigDecimal input, int scale, int precision) {
    // Truncate or pad to set the input to the correct scale
    input = input.setScale(scale, BigDecimal.ROUND_HALF_UP);

    return input.unscaledValue().intValue();
  }

  public static BigDecimal getBigDecimalFromPrimitiveTypes(int input, int scale, int precision) {
    return BigDecimal.valueOf(input, scale);
  }

  public static BigDecimal getBigDecimalFromPrimitiveTypes(long input, int scale, int precision) {
    return BigDecimal.valueOf(input, scale);
  }

  public static int compareDenseBytes(DrillBuf left, int leftStart, boolean leftSign, DrillBuf right, int rightStart, boolean rightSign, int width) {

    int invert = 1;

    /* If signs are different then simply look at the
     * sign of the two inputs and determine which is greater
     */
    if (leftSign != rightSign) {

      return((leftSign == true) ? -1 : 1);
    } else if(leftSign == true) {
      /* Both inputs are negative, at the end we will
       * have to invert the comparison
       */
      invert = -1;
    }

    int cmp = 0;

    for (int i = 0; i < width; i++) {
      byte leftByte  = left.getByte(leftStart + i);
      byte rightByte = right.getByte(rightStart + i);
      // Unsigned byte comparison
      if ((leftByte & 0xFF) > (rightByte & 0xFF)) {
        cmp = 1;
        break;
      } else if ((leftByte & 0xFF) < (rightByte & 0xFF)) {
        cmp = -1;
        break;
      }
    }
    cmp *= invert; // invert the comparison if both were negative values

    return cmp;
  }

  public static int getIntegerFromSparseBuffer(DrillBuf buffer, int start, int index) {
    int value = buffer.getInt(start + (index * 4));

    if (index == 0) {
      /* the first byte contains sign bit, return value without it */
      value = (value & 0x7FFFFFFF);
    }
    return value;
  }

  public static void setInteger(DrillBuf buffer, int start, int index, int value) {
    buffer.setInt(start + (index * 4), value);
  }

  public static int compareSparseBytes(DrillBuf left, int leftStart, boolean leftSign, int leftScale, int leftPrecision, DrillBuf right, int rightStart, boolean rightSign, int rightPrecision, int rightScale, int width, int nDecimalDigits, boolean absCompare) {

    int invert = 1;

    if (absCompare == false) {
      if (leftSign != rightSign) {
        return (leftSign == true) ? -1 : 1;
      }

      // Both values are negative invert the outcome of the comparison
      if (leftSign == true) {
        invert = -1;
      }
    }

    int cmp = compareSparseBytesInner(left, leftStart, leftSign, leftScale, leftPrecision, right, rightStart, rightSign, rightPrecision, rightScale, width, nDecimalDigits);
    return cmp * invert;
  }

  public static int compareSparseBytesInner(DrillBuf left, int leftStart, boolean leftSign, int leftScale, int leftPrecision, DrillBuf right, int rightStart, boolean rightSign, int rightPrecision, int rightScale, int width, int nDecimalDigits) {
    /* compute the number of integer digits in each decimal */
    int leftInt  = leftPrecision - leftScale;
    int rightInt = rightPrecision - rightScale;

    /* compute the number of indexes required for storing integer digits */
    int leftIntRoundedUp = org.apache.drill.exec.util.DecimalUtility.roundUp(leftInt);
    int rightIntRoundedUp = org.apache.drill.exec.util.DecimalUtility.roundUp(rightInt);

    /* compute number of indexes required for storing scale */
    int leftScaleRoundedUp = org.apache.drill.exec.util.DecimalUtility.roundUp(leftScale);
    int rightScaleRoundedUp = org.apache.drill.exec.util.DecimalUtility.roundUp(rightScale);

    /* compute index of the most significant integer digits */
    int leftIndex1 = nDecimalDigits - leftScaleRoundedUp - leftIntRoundedUp;
    int rightIndex1 = nDecimalDigits - rightScaleRoundedUp - rightIntRoundedUp;

    int leftStopIndex = nDecimalDigits - leftScaleRoundedUp;
    int rightStopIndex = nDecimalDigits - rightScaleRoundedUp;

    /* Discard the zeroes in the integer part */
    while (leftIndex1 < leftStopIndex) {
      if (getIntegerFromSparseBuffer(left, leftStart, leftIndex1) != 0) {
        break;
      }

      /* Digit in this location is zero, decrement the actual number
       * of integer digits
       */
      leftIntRoundedUp--;
      leftIndex1++;
    }

    /* If we reached the stop index then the number of integers is zero */
    if (leftIndex1 == leftStopIndex) {
      leftIntRoundedUp = 0;
    }

    while (rightIndex1 < rightStopIndex) {
      if (getIntegerFromSparseBuffer(right, rightStart, rightIndex1) != 0) {
        break;
      }

      /* Digit in this location is zero, decrement the actual number
       * of integer digits
       */
      rightIntRoundedUp--;
      rightIndex1++;
    }

    if (rightIndex1 == rightStopIndex) {
      rightIntRoundedUp = 0;
    }

    /* We have the accurate number of non-zero integer digits,
     * if the number of integer digits are different then we can determine
     * which decimal is larger and needn't go down to comparing individual values
     */
    if (leftIntRoundedUp > rightIntRoundedUp) {
      return 1;
    }
    else if (rightIntRoundedUp > leftIntRoundedUp) {
      return -1;
    }

    /* The number of integer digits are the same, set the each index
     * to the first non-zero integer and compare each digit
     */
    leftIndex1 = nDecimalDigits - leftScaleRoundedUp - leftIntRoundedUp;
    rightIndex1 = nDecimalDigits - rightScaleRoundedUp - rightIntRoundedUp;

    while (leftIndex1 < leftStopIndex && rightIndex1 < rightStopIndex) {
      if (getIntegerFromSparseBuffer(left, leftStart, leftIndex1) > getIntegerFromSparseBuffer(right, rightStart, rightIndex1)) {
        return 1;
      }
      else if (getIntegerFromSparseBuffer(right, rightStart, rightIndex1) > getIntegerFromSparseBuffer(left, leftStart, leftIndex1)) {
        return -1;
      }

      leftIndex1++;
      rightIndex1++;
    }

    /* The integer part of both the decimal's are equal, now compare
     * each individual fractional part. Set the index to be at the
     * beginning of the fractional part
     */
    leftIndex1 = leftStopIndex;
    rightIndex1 = rightStopIndex;

    /* Stop indexes will be the end of the array */
    leftStopIndex = nDecimalDigits;
    rightStopIndex = nDecimalDigits;

    /* compare the two fractional parts of the decimal */
    while (leftIndex1 < leftStopIndex && rightIndex1 < rightStopIndex) {
      if (getIntegerFromSparseBuffer(left, leftStart, leftIndex1) > getIntegerFromSparseBuffer(right, rightStart, rightIndex1)) {
        return 1;
      }
      else if (getIntegerFromSparseBuffer(right, rightStart, rightIndex1) > getIntegerFromSparseBuffer(left, leftStart, leftIndex1)) {
        return -1;
      }

      leftIndex1++;
      rightIndex1++;
    }

    /* Till now the fractional part of the decimals are equal, check
     * if one of the decimal has fractional part that is remaining
     * and is non-zero
     */
    while (leftIndex1 < leftStopIndex) {
      if (getIntegerFromSparseBuffer(left, leftStart, leftIndex1) != 0) {
        return 1;
      }
      leftIndex1++;
    }

    while(rightIndex1 < rightStopIndex) {
      if (getIntegerFromSparseBuffer(right, rightStart, rightIndex1) != 0) {
        return -1;
      }
      rightIndex1++;
    }

    /* Both decimal values are equal */
    return 0;
  }

  public static BigDecimal getBigDecimalFromByteArray(byte[] bytes, int start, int length, int scale) {
    byte[] value = Arrays.copyOfRange(bytes, start, start + length);
    BigInteger unscaledValue = new BigInteger(value);
    return new BigDecimal(unscaledValue, scale);
  }

  public static void roundDecimal(DrillBuf result, int start, int nDecimalDigits, int desiredScale, int currentScale) {
    int newScaleRoundedUp  = org.apache.drill.exec.util.DecimalUtility.roundUp(desiredScale);
    int origScaleRoundedUp = org.apache.drill.exec.util.DecimalUtility.roundUp(currentScale);

    if (desiredScale < currentScale) {

      boolean roundUp = false;

      //Extract the first digit to be truncated to check if we need to round up
      int truncatedScaleIndex = desiredScale + 1;
      if (truncatedScaleIndex <= currentScale) {
        int extractDigitIndex = nDecimalDigits - origScaleRoundedUp -1;
        extractDigitIndex += org.apache.drill.exec.util.DecimalUtility.roundUp(truncatedScaleIndex);
        int extractDigit = getIntegerFromSparseBuffer(result, start, extractDigitIndex);
        int temp = org.apache.drill.exec.util.DecimalUtility.MAX_DIGITS - (truncatedScaleIndex % org.apache.drill.exec.util.DecimalUtility.MAX_DIGITS);
        if (temp != 0) {
          extractDigit = extractDigit / (int) (Math.pow(10, temp));
        }
        if ((extractDigit % 10)  > 4) {
          roundUp = true;
        }
      }

      // Get the source index beyond which we will truncate
      int srcIntIndex = nDecimalDigits - origScaleRoundedUp - 1;
      int srcIndex = srcIntIndex + newScaleRoundedUp;

      // Truncate the remaining fractional part, move the integer part
      int destIndex = nDecimalDigits - 1;
      if (srcIndex != destIndex) {
        while (srcIndex >= 0) {
          setInteger(result, start, destIndex--, getIntegerFromSparseBuffer(result, start, srcIndex--));
        }

        // Set the remaining portion of the decimal to be zeroes
        while (destIndex >= 0) {
          setInteger(result, start, destIndex--, 0);
        }
        srcIndex = nDecimalDigits - 1;
      }

      // We truncated the decimal digit. Now we need to truncate within the base 1 billion fractional digit
      int truncateFactor = org.apache.drill.exec.util.DecimalUtility.MAX_DIGITS - (desiredScale % org.apache.drill.exec.util.DecimalUtility.MAX_DIGITS);
      if (truncateFactor != org.apache.drill.exec.util.DecimalUtility.MAX_DIGITS) {
        truncateFactor = (int) Math.pow(10, truncateFactor);
        int fractionalDigits = getIntegerFromSparseBuffer(result, start, nDecimalDigits - 1);
        fractionalDigits /= truncateFactor;
        setInteger(result, start, nDecimalDigits - 1, fractionalDigits * truncateFactor);
      }

      // Finally round up the digit if needed
      if (roundUp == true) {
        srcIndex = nDecimalDigits - 1;
        int carry;
        if (truncateFactor != org.apache.drill.exec.util.DecimalUtility.MAX_DIGITS) {
          carry = truncateFactor;
        } else {
          carry = 1;
        }

        while (srcIndex >= 0) {
          int value = getIntegerFromSparseBuffer(result, start, srcIndex);
          value += carry;

          if (value >= org.apache.drill.exec.util.DecimalUtility.DIGITS_BASE) {
            setInteger(result, start, srcIndex--, value % org.apache.drill.exec.util.DecimalUtility.DIGITS_BASE);
            carry = value / org.apache.drill.exec.util.DecimalUtility.DIGITS_BASE;
          } else {
            setInteger(result, start, srcIndex--, value);
            carry = 0;
            break;
          }
        }
      }
    } else if (desiredScale > currentScale) {
      // Add fractional digits to the decimal

      // Check if we need to shift the decimal digits to the left
      if (newScaleRoundedUp > origScaleRoundedUp) {
        int srcIndex  = 0;
        int destIndex = newScaleRoundedUp - origScaleRoundedUp;

        // Check while extending scale, we are not overwriting integer part
        while (srcIndex < destIndex) {
          if (getIntegerFromSparseBuffer(result, start, srcIndex++) != 0) {
            throw new org.apache.drill.common.exceptions.DrillRuntimeException("Truncate resulting in loss of integer part, reduce scale specified");
          }
        }

        srcIndex = 0;
        while (destIndex < nDecimalDigits) {
          setInteger(result, start, srcIndex++, getIntegerFromSparseBuffer(result, start, destIndex++));
        }

        // Clear the remaining part
        while (srcIndex < nDecimalDigits) {
          setInteger(result, start, srcIndex++, 0);
        }
      }
    }
  }

  public static int getFirstFractionalDigit(int decimal, int scale) {
    if (scale == 0) {
      return 0;
    }
    int temp = (int) adjustScaleDivide(decimal, scale - 1);
    return Math.abs(temp % 10);
  }

  public static int getFirstFractionalDigit(long decimal, int scale) {
    if (scale == 0) {
      return 0;
    }
    long temp = adjustScaleDivide(decimal, scale - 1);
    return (int) (Math.abs(temp % 10));
  }

  public static int getFirstFractionalDigit(DrillBuf data, int scale, int start, int nDecimalDigits) {
    if (scale == 0) {
      return 0;
    }

    int index = nDecimalDigits - roundUp(scale);
    return (int) (adjustScaleDivide(data.getInt(start + (index * INTEGER_SIZE)), MAX_DIGITS - 1));
  }

  public static int compareSparseSamePrecScale(DrillBuf left, int lStart, byte[] right, int length) {
    // check the sign first
    boolean lSign = (left.getInt(lStart) & 0x80000000) != 0;
    boolean rSign = ByteFunctionHelpers.getSign(right);
    int cmp = 0;

    if (lSign != rSign) {
      return (lSign == false) ? 1 : -1;
    }

    // invert the comparison if we are comparing negative numbers
    int invert = (lSign == true) ? -1 : 1;

    // compare byte by byte
    int n = 0;
    while (n < length/4) {
      int leftInt = Decimal38SparseHolder.getInteger(n, lStart, left);
      int rightInt = ByteFunctionHelpers.getInteger(right, n);
      if (leftInt != rightInt) {
        cmp =  (leftInt - rightInt ) > 0 ? 1 : -1;
        break;
      }
      n++;
    }
    return cmp * invert;
  }
}

